Conversation
Node-based CLI that copies SDK upload/error-handling logic for e2e testing against mock server. Includes E2E_TEST_SUITES for selective test execution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make apiHost and cdnHost optional in the CLI input type contract. Add e2e-cli README documenting the input/output format and parameters. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace copied SDK functions with real imports from the SDK source. Events now flow through the full pipeline: SegmentClient → Timeline → SegmentDestination (batch chunking) → QueueFlushingPlugin (queue) → uploadEvents HTTP POST. Three minimal stubs replace React Native runtime dependencies: - react-native: mocks AppState, NativeModules, Platform - @segment/sovran-react-native: re-exports real store.ts + bridge.ts, bypassing the RN bridge entry point - react-native-get-random-values: no-op (Node.js has native crypto) Uses esbuild to bundle CLI + SDK source + stubs into dist/cli.js. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Per-SDK config and convenience script for the generic test runner in sdk-e2e-tests. Run ./e2e-cli/run-e2e.sh to build and test locally. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded env vars and direct npm test call with ./scripts/run-tests.sh which reads e2e-config.json for test configuration. This ensures CI uses the same config as local runs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The e2e-cli directory has its own tsconfig and is not part of the SDK's lint setup. Excluding it prevents ESLint parsing errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
e2e-cli/src/cli.ts
Outdated
| break; | ||
| case 'screen': | ||
| await client.screen( | ||
| evt.event!, |
There was a problem hiding this comment.
also we should probably handle the cases where a field on event is missing instead of using null suppression ! operator. that way if some one passes an invalid event to --input, we don't get a vague runtime error
There was a problem hiding this comment.
Hrm. The AI's response was to wrap everything in if(!__) throw, is that the solution you were thinking of? I was wondering if there's a more useful test case in getting an SDK error, but those often take string, so evt.name ?? '' or type casting the possibly undefined var as string is... maybe not a good test. Thoughts?
There was a problem hiding this comment.
i would just do something like this. claude may have gone a little bit overboard, but the main point is that if an event has an invalid field which is required for one of the client functions (client.track() for example), we wanna make sure to skip that event with continue; and warn we're skipping that event because evt.event was null.
for (const evt of sequence.events) {
// Validate event has a type
if (!evt.type) {
console.warn('[WARN] Skipping event: missing event type', evt);
continue;
}
try {
switch (evt.type) {
case 'track': {
// Required: event name
if (!evt.event || typeof evt.event !== 'string') {
console.warn(`[WARN] Skipping track event: missing or invalid event name`, evt);
continue;
}
// Optional: properties (validate if present)
const properties = evt.properties as JsonMap | undefined;
if (evt.properties !== undefined && typeof evt.properties !== 'object') {
console.warn(`[WARN] Track event "${evt.event}" has invalid properties, proceeding without them`);
}
await client.track(evt.event, properties);
break;
}
case 'identify': {
// Optional userId (Segment allows anonymous identify)
// Optional traits (validate if present)
const traits = evt.traits as JsonMap | undefined;
if (evt.traits !== undefined && typeof evt.traits !== 'object') {
console.warn(`[WARN] Identify event has invalid traits, proceeding without them`);
}
await client.identify(evt.userId, traits);
break;
}
case 'screen':
case 'page': {
// Required: screen/page name
if (!evt.name || typeof evt.name !== 'string') {
console.warn(`[WARN] Skipping ${evt.type} event: missing or invalid name`, evt);
continue;
}
// Optional: properties (validate if present)
const properties = evt.properties as JsonMap | undefined;
if (evt.properties !== undefined && typeof evt.properties !== 'object') {
console.warn(`[WARN] Screen "${evt.name}" has invalid properties, proceeding without them`);
}
await client.screen(evt.name, properties);
break;
}
case 'group': {
// Required: groupId
if (!evt.groupId || typeof evt.groupId !== 'string') {
console.warn(`[WARN] Skipping group event: missing or invalid groupId`, evt);
continue;
}
// Optional: traits (validate if present)
const traits = evt.traits as JsonMap | undefined;
if (evt.traits !== undefined && typeof evt.traits !== 'object') {
console.warn(`[WARN] Group event for "${evt.groupId}" has invalid traits, proceeding without them`);
}
await client.group(evt.groupId, traits);
break;
}
case 'alias': {
// Required: userId
if (!evt.userId || typeof evt.userId !== 'string') {
console.warn(`[WARN] Skipping alias event: missing or invalid userId`, evt);
continue;
}
await client.alias(evt.userId);
break;
}
default:
console.warn(`[WARN] Skipping event: unknown event type "${evt.type}"`, evt);
continue;
}
} catch (error) {
// Log but don't fail the entire sequence if one event fails
console.error(`[ERROR] Failed to process ${evt.type} event:`, error, evt);
continue;
}
}
e2e-cli/src/cli.ts
Outdated
| break; | ||
| case 'group': | ||
| await client.group( | ||
| evt.event!, |
e2e-cli/src/stubs/sovran.ts
Outdated
| Persistor, | ||
| PersistenceConfig, | ||
| } from '../../../packages/sovran/src/persistor/persistor'; | ||
| export { AsyncStoragePersistor } from '../../../packages/sovran/src/persistor/async-storage-persistor'; |
There was a problem hiding this comment.
exporting AsyncStoragePersistor will cause @react-native-async-storage/async-storage to be required which won't work with the CLI
| }; | ||
| } | ||
|
|
||
| interface CLIOutput { |
There was a problem hiding this comment.
in the e2e project we have sentBatches defined as a required parameter, but its not defined here in the cli tool. also it might be worth creating a library that exports common types used by both the client and server, but i'll leave that value judgement to you whether there are enough shared types to justify it.
There was a problem hiding this comment.
Most of the CLIs are different languages, seems like a lot of infra to get that working everywhere at the moment.
| } | ||
|
|
||
| for (const evt of sequence.events) { | ||
| switch (evt.type) { |
e2e-cli/src/cli.ts
Outdated
| events: Array<{ | ||
| type: string; | ||
| event?: string; | ||
| userId?: string; |
There was a problem hiding this comment.
missing anonymousId, messageId, and timestamp.
e2e-cli/src/cli.ts
Outdated
| apiHost?: string; | ||
| cdnHost?: string; | ||
| sequences: Array<{ | ||
| delayMs: number; |
There was a problem hiding this comment.
i would separate the sequences and events into their own type like you did in the main repo
e2e-cli/src/cli.ts
Outdated
| }>; | ||
| }>; | ||
| config?: { | ||
| flushAt?: number; |
There was a problem hiding this comment.
same here i would split config into its own type, and add the missing maxRetries field
- Align types with sdk-e2e-tests (AnalyticsEvent, EventSequence, CLIConfig, CLIOutput with sentBatches) - Fix screen to use evt.name instead of evt.event - Fix group to use evt.groupId instead of evt.event - Replace non-null assertions with validation errors - Add page event type (throws: not supported on mobile) - Add default case for unknown event types - Remove AsyncStoragePersistor export from sovran stub (pulls in @react-native-async-storage which breaks Node.js) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mobile SDKs don't have a page method. Kotlin and Swift CLIs already map page → screen silently. Match that behavior here instead of throwing an error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CLI should be a thin pass-through so e2e tests observe actual SDK behavior for missing fields, not CLI pre-validation errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
No description provided.